/**
 * @file virtio.h
 * @date 23.04.30
 * @author xin
 * @brief 定义virtio设备，包括mmio接口，virtio描述符
 * @brief 参考规范：../../other/工具/QEMU/virtio-v1.1.pdf
 * @version 0.1
 * @status Stable
*/

// virtio mmio控制寄存器，从0x10001000开始映射
// 参考qemu的virtio_mmio.h
#define VIRTIO_MMIO_MAGIC_VALUE     0x000   // Magic value ("virt" string) : 0x74726976
#define VIRTIO_MMIO_VERSION		0x004 // version; should be 2
#define VIRTIO_MMIO_DEVICE_ID		0x008 // device type; 1 is net, 2 is disk
#define VIRTIO_MMIO_VENDOR_ID		0x00c // 0x554d4551
#define VIRTIO_MMIO_DEVICE_FEATURES	0x010
#define VIRTIO_MMIO_DRIVER_FEATURES	0x020
// 写入该寄存器选择虚拟队列：QueueNumMax, QueueNum, QueueReady, QueueDescLow，
// QueueDescHigh, QueueAvailLow, QueueAvailHigh, QueueUsedLow和
// QueueUsedHigh, 第一个队列的索引为0x0，write-only
#define VIRTIO_MMIO_QUEUE_SEL		0x030 
// 返回就绪设备队列的最大数目或0x0（队列不可用），适用于QUEUE_SEL选择的队列，read-only
#define VIRTIO_MMIO_QUEUE_NUM_MAX	0x034
// 返回队列中元素个数
// 写入该寄存器用于通知驱动要用的队列长度，适用于QUEUE_SEL选择的队列，write-only
#define VIRTIO_MMIO_QUEUE_NUM		0x038 
// 向该寄存器写入1以通知设备可以执行来自虚拟队列的请求。读取该寄存器将返回最后写入该寄存器的值
// 适用于QUEUE_SEL选择的队列 
#define VIRTIO_MMIO_QUEUE_READY		0x044
// 向该寄存器写入值通知队列有新缓冲区待处理
// 未确认VIRTIO_F_NOTIFICATION_DATA时，写入的值为队列索引
// 否则Notification数据格式如下：
// le32 {
//  vqn : 16;
//  next_off : 15;
//  next_wrap : 1;
// };
// write-only
#define VIRTIO_MMIO_QUEUE_NOTIFY	0x050
// 中断状态
// 返回导致设备中断被断言的事件的位掩码
// 可能的中断被断言的情况：
// 使用缓冲区通知-bit0-因为设备在至少一个活动虚拟队列中使用缓冲区
// 配置更改通知-bit1-因为设备配置改变
// read-only
#define VIRTIO_MMIO_INTERRUPT_STATUS	0x060
// 中断确认
// 写入在INTERRUPT_STATUS中定义的位值到该寄存器
// 以通知设备导致中断的事件已被处理
// write-only
#define VIRTIO_MMIO_INTERRUPT_ACK	0x064
// 返回当前设备状态标志
// 写入则设置设备状态标志
//    non-zero--指示驱动进度
//    0--触发设备复位
// read/write
#define VIRTIO_MMIO_STATUS		0x070 
// 描述符表的物理地址
// write-only
#define VIRTIO_MMIO_QUEUE_DESC_LOW	0x080
#define VIRTIO_MMIO_QUEUE_DESC_HIGH	0x084
// 可用环的物理地址
// write-only
#define VIRTIO_MMIO_DRIVER_DESC_LOW	0x090 
#define VIRTIO_MMIO_DRIVER_DESC_HIGH	0x094
// 已使用环的物理地址
// write-only
#define VIRTIO_MMIO_DEVICE_DESC_LOW	0x0a0
#define VIRTIO_MMIO_DEVICE_DESC_HIGH	0x0a4

// 状态寄存器的位
// 参考qemu的virtio_config.h
#define VIRTIO_CONFIG_S_ACKNOWLEDGE	1
#define VIRTIO_CONFIG_S_DRIVER		2
#define VIRTIO_CONFIG_S_DRIVER_OK	4
#define VIRTIO_CONFIG_S_FEATURES_OK	8

// 设备特性位
#define VIRTIO_BLK_F_RO              5	/* Disk is read-only */
#define VIRTIO_BLK_F_SCSI            7	/* Supports scsi command passthru */
#define VIRTIO_BLK_F_CONFIG_WCE     11	/* Writeback mode available in config */
#define VIRTIO_BLK_F_MQ             12	/* support more than one vq */
#define VIRTIO_F_ANY_LAYOUT         27
#define VIRTIO_RING_F_INDIRECT_DESC 28
#define VIRTIO_RING_F_EVENT_IDX     29

// virtio描述符的数量必须为双数
#define NUM 8

// 单个虚拟队列描述符，参考规范
// 驱动程序为设备使用的缓冲区
struct virtq_desc {
    uint64 addr;  // 物理地址
    uint32 len;   // 缓冲区长度
    uint16 flags; // 缓冲区的标志
    uint16 next;  // 缓冲区间连接符，当flags & NEXT非零时有效
};
#define VRING_DESC_F_NEXT  1 // 是否与另一个缓冲区连接
#define VRING_DESC_F_WRITE 2 // 设备只写（若flags在该位不置位则设备只读）

// 完整的空闲/可使用环，参考规范
// 驱动程序使用available ring为设备提供缓冲区
// 每个ring项都指向描述符链的头部
// 只由驱动程序写入，由设备读取
struct virtq_avail {
  uint16 flags;     // always zero
  uint16 idx;       // 下一个ring项的索引。驱动程序将把下一个描述符项放在环中的位置(对队列大小取模)。从0开始
  uint16 ring[NUM]; // 数组的每一项都是某一个描述符链的头部数字
  uint16 unused;
};

// one entry in the "used" ring, with which the
// device tells the driver about completed requests.
// used ring中的一项
struct virtq_used_elem {
  uint32 id;   // 描述符链的头部索引数字
  uint32 len; // 缓冲区长度
};

// 设备在使用完缓冲区后返回缓冲区的地方
// 它只被设备写入，由驱动程序读取。
struct virtq_used {
  uint16 flags; // always zero
  uint16 idx;   // 下一个ring项的索引
  struct virtq_used_elem ring[NUM];
};

// these are specific to virtio block devices, e.g. disks,
// described in Section 5.2 of the spec.

#define VIRTIO_BLK_T_IN  0 // read the disk
#define VIRTIO_BLK_T_OUT 1 // write the disk

// 驱动程序将请求排队到virtqueue，由设备处理
// 请求格式：
struct virtio_blk_req {
  uint32 type;      // 有两种类型：VIRTIO_BLK_T_IN or ..._OUT
  uint32 reserved;  
  uint64 sector;    // 起始偏移量(以512字节为单位)
};
